/*
 * This file is part of the URI Template library.
 *
 * For licensing information please see the file license.txt included in the release.
 * A copy of this licence can also be found at 
 *   http://www.opensource.org/licenses/artistic-license-2.0.php
 */
package org.weborganic.furi;

import java.util.Hashtable;
import java.util.Map;

/**
 * A convenience class is to bind variables to resolvers in a set of URI
 * patterns.
 * 
 * Variables can be bound to a resolver by type or by name.
 * 
 * To assign a {@link VariableResolver} to variables of a specific type, use
 * {@link #bindType(String, VariableResolver)}.
 * 
 * The following example will bind all variables typed <code>int</code> to
 * return the corresponding integer value.
 * 
 * <pre>
 * VariableBinder binder = new VariableBinder();
 * b.bindType("int", new VariableResolver() {
 * 	public boolean exists(String v) {
 * 		return v.matches("\\d+");
 * 	}
 * 
 * 	public Integer resolve(String v) {
 * 		return exists(v) ? Integer.valueOf(v) : null;
 * 	};
 * });
 * </pre>
 * 
 * To assign a {@link VariableResolver} to variables of a specific name, use
 * {@link #bindName(String, VariableResolver)}.
 *
 * The following example will bind all variables typed <code>int</code> to
 * return the corresponding integer value.
 * 
 * <pre>
 * VariableBinder binder = new VariableBinder();
 * b.bindName("name", new VariableResolver() {
 * 	public boolean exists(String v) {
 * 		return true;
 * 	}
 * 
 * 	public Integer resolve(String v) {
 * 		return exists(v) ? Integer.valueOf(v) : null;
 * 	};
 * });
 * </pre>
 * 
 * 
 * @author Christophe Lauret
 * @version 11 June 2009
 */
public class VariableBinder {

	/**
	 * The default resolver accepts everything and resolves all values to
	 * themselves.
	 */
	private final static VariableResolver DEFAULT_RESOLVER = new VariableResolver() {
		public boolean exists(String value) {
			return true;
		}

		public Object resolve(String value) {
			return value;
		}
	};

	/**
	 * Maps a variable names to a resolver.
	 */
	private Map<String, VariableResolver> _byname = new Hashtable<String, VariableResolver>();

	/**
	 * Maps a variable types to a resolver.
	 */
	private Map<String, VariableResolver> _bytype = new Hashtable<String, VariableResolver>();

	/**
	 * Binds the variables with the specified name to the specified resolver.
	 * 
	 * @deprecated use #bindName() or #bindType() instead
	 * 
	 * @param name
	 *            The name of the variable.
	 * @param resolver
	 *            The resolver to use with these variables.
	 */
	public void bind(String name, VariableResolver resolver) {
		this._byname.put(name, resolver);
	}

	/**
	 * Binds the variables with the specified name to the specified resolver.
	 * 
	 * @param name
	 *            The name of the variable.
	 * @param resolver
	 *            The resolver to use with these variables.
	 */
	public void bindName(String name, VariableResolver resolver) {
		this._byname.put(name, resolver);
	}

	/**
	 * Binds the variables with the specified name to the specified resolver.
	 * 
	 * @param type
	 *            The variable type.
	 * @param resolver
	 *            The resolver to use with these variables.
	 */
	public void bindType(String type, VariableResolver resolver) {
		this._bytype.put(type, resolver);
	}

	/**
	 * Returns the resolver used for the variable of the specified name or type.
	 * 
	 * <p>
	 * By default, looks for the resolver assigned to the specified variable
	 * name; if no resolver is bound to the variable name, it will return the
	 * resolver bound to the given variable type.
	 * 
	 * <p>
	 * This method does not return <code>null</code>. If the specified variable
	 * name or type is not bound to any resolver the default resolver if
	 * returned instead.
	 * 
	 * @param name
	 *            The name of the variable.
	 * @param type
	 *            The type of the variable.
	 * 
	 * @return the corresponding resolver.
	 */
	public VariableResolver getResolver(String name, VariableType type) {
		VariableResolver resolver = this._byname.get(name);

		// try to find a resolver by type
		if (resolver == null && type != null) {
			resolver = this._bytype.get(type.getName());
		}
		// fall back on the default otherwise
		return resolver != null ? resolver : DEFAULT_RESOLVER;
	}

	/**
	 * Returns the resolver used for the variable of the specified name.
	 * 
	 * <p>
	 * This method does not return <code>null</code>. If the specified variable
	 * name is no bound to any resolver the default resolver if returned
	 * instead.
	 * 
	 * @param name
	 *            The name of the variables.
	 * 
	 * @return the corresponding resolver.
	 */
	public VariableResolver getResolver(String name) {
		VariableResolver resolver = this._byname.get(name);

		return resolver != null ? resolver : DEFAULT_RESOLVER;
	}

	/**
	 * Returns the resolver used for the variable of the specified type.
	 * 
	 * <p>
	 * This method does not return <code>null</code>. If the specified variable
	 * name is no bound to any resolver the default resolver if returned
	 * instead.
	 * 
	 * @param type
	 *            The type of the variable.
	 * 
	 * @return the corresponding resolver.
	 */
	public VariableResolver getResolver(VariableType type) {
		if (type == null) {
			return DEFAULT_RESOLVER;
		}
		VariableResolver resolver = this._bytype.get(type.getName());

		return resolver != null ? resolver : DEFAULT_RESOLVER;
	}

	/**
	 * Indicates whether the given variable name is bound to a VariableResolver.
	 * 
	 * @param name
	 *            The variable name.
	 * 
	 * @return <code>true</code> if a given variable resolver is bound to the
	 *         specific name; <code>false</code> otherwise (including if the
	 *         name is <code>null</code>.
	 */
	public boolean isNameBound(String name) {
		if (name == null) {
			return false;
		}
		return this._byname.containsKey(name);
	}

	/**
	 * Indicates whether the given variable type is bound to a VariableResolver.
	 * 
	 * @param type
	 *            The variable type.
	 * 
	 * @return <code>true</code> if a given variable resolver is bound to the
	 *         specific type; <code>false</code> otherwise (including if the
	 *         type is <code>null</code>.
	 */
	public boolean isTypeBound(String type) {
		if (type == null) {
			return false;
		}
		return this._bytype.containsKey(type);
	}

}
